Keep and carry on.
post @ 2023-12-23

欢迎来到信息安全数学基础MFIS!(Math Foundation of Information Security我自己翻的也不知道对不对hhh)
这里上传了所有四次实验的报告,所有的实验都是我自己跑过的数据和截图,请放心食用!
因为这门课程性质和成分上的特殊性,好了不卖关子了,人话就是这是一门核!心!课!保研人听了打鸡血,边缘人听了想落泪…所以可见实验成为成绩的一部分也称了卷的境地,验收的时候我前面的同学一个题目用了四五种方法,让我这个想到两种方法都沾沾自喜的人无地自容,紧接着到我验收瞬间成了哑巴…可见大家对这门课的重视程度,所以…emm…卷起来吧!信安er!
Anyway希望这个tag里的内容可以帮到你!

Read More
post @ 2023-12-23

欢迎来到软件安全测试SST!(Software Security Testing我自己翻的也不知道对不对hhh)
这里上传了所有六次实验的报告(虽然老师说可以选择不用都做hhh),所有的实验都是我自己跑过的数据和截图,请放心食用!
如果有的报告没有大title,是因为那次没有轮到我上交所以写的不是正式的实验报告,但也写了个简洁的report方便自己复习和验收前回顾一下hhh
这门课算得上我自己最喜欢的课程了,所以也一点不会觉得实验有些多(说到这,我想起来上学期网络那门课有4次实验我都嫌好多&头大…咳咳,网络,怎么到哪我都在吐槽你…)
Anyway希望这个tag里的内容可以帮到你!

实验任务一览:

lab1: Environment Variable and SetUID

lab2: Shellshock Attack

lab3: Buffer Overflow

lab4: Format String

lab5: DirtyCow Attack

lab6: 模糊测试

ps.前五次来自教材《计算机安全导论:深度实践》上的章节最后的seedlab,最后一次是老师自定义的题目。咳咳关于最后一次实验其实自我感觉还不太深入,只是浅浅过了fuzzing的流程而已。因为当时刚比赛回来紧接着又去跑广州办美签再加上核心课的期末考试(没错就是你信息安全数学基础!)时间经历都比较匆忙,老师了解到也很理解,但我还是感觉想动手过一下模糊测试,所以…所以虚拟机跑了四天电脑一直都没关机直到验收!一直占了我一个cpu核啊!emm麻鸭我话是不是太多了,这不是树洞啊!不能一直碎碎念啊!!(好的小跑离开ing)

Read More
post @ 2023-12-23

Afl实验报告

一、对c++程序进行afl

1.环境准备:

下载好afl,并准备两个文件夹用于存放输入的测试例子和输出的crash

准备afl插桩程序

写一个简单的C++程序afl_test.c,主要包含两个函数:main和vuln

main函数是程序的入口点,首先创建了一个字符数组buf,然后使用gets函数从标准输入读取字符串到buf。使用不安全的gets()函数,它不会去检查边界,这会导致缓冲区溢出。这里存在一个栈溢出漏洞,因为gets函数不会检查输入的长度,如果输入的字符串超过buf的大小,就会导致栈溢出。然后,使用printf函数打印buf,这里存在一个格式化字符串漏洞,因为printf函数会将buf中的内容当作格式字符串来处理,如果buf中包含%字符,就可能导致未预期的行为。最后,调用vuln函数处理buf。

vuln函数接受一个字符串作为参数。首先,计算字符串的长度。然后,检查字符串的第一个字符和长度。如果第一个字符是’A’并且长度是66,或者第一个字符是’F’并且长度是6,就会调用raise函数发送SIGSEGV信号,导致程序异常退出。否则,打印”It is good!“。

使用afl的gcc将afl_test.c进行编译,编译完成后出现了新文件afl_test

在AFL编译文件时候afl-gcc会在规定位置插入桩代码,可以理解为一个个的探针(但是没有暂停功能),在后续fuzz的过程中会根据这些桩代码进行路径探索,测试等。

在fuzz_in中还需要创建一个testcase文件,写入aaa。

然后启动afl-fuzz程序,将testcase(输入的测试文件)作为程序的输入执行程序,afl会在这个testcase的基础上进行自动变异输入,使得程序产生crash,产生了crash就会被记录起来。

3.开始Fuzz

4.输出crash分析

crashes文件夹里面是产生crash的样例,hangs里面是产生超时的样例,queue里面是每个不同执行路径的测试用例。

首先回顾测试程序中会导致退出的三种情形:首字符为A且长度为66;首字符为F且长度为6;栈溢出漏洞

用xxd查看第一个crash样例的完整内容,符合栈溢出导致的程序退出情况:

用xxd查看第二个crash样例的完整内容,符合首字符是F且长度为6导致程序退出的情况:

用xxd查看第三个crash样例的完整内容,符合栈溢出导致的程序退出情况:

二、对cpython库进行afl

1.配置cpython环境

下载cpython库,并将编译器指定为 afl-gcc,然后再进行编译:

apt-get update

sudo apt-get install -y build-essential libncursesw5-dev libreadline-dev libssl-dev libgdbm-dev libc6-dev libsqlite3-dev tk-dev libbz2-dev zlib1g-dev libffi-dev

git clone https://gitee\.com/mirrors/cpython\.git

cd cpython

./configure CC=”afl-gcc” CXX=”afl-g++“

make

2.开始fuzz

在fuzz_in文件夹下出创建testcase输入语料aaaaa,然后开始fuzz:

3.输出结果分析

CPython 是最常用的 Python 实现,它是官方用 C 编写的 python 实现,所以crash几乎发现不了也很正常,但还是在fuzz一整天(24小时)内发现了6个超时案例,对输出文件夹fuzz_out下的hangs文件夹中的输出例子进行分析,hangs文件夹放超时的例子,共有80个:

依次用xxd命令查看完整内容,以第一个例子为典型看出,仅仅18个字符就可以造成官方实现的cpython库超时:

三、Afl界面分析以及遇到的问题

1.process timing

展示了当前fuzzer的运行时间、最近一次发现新执行路径的时间、最近一次崩溃的时间、最近一次超时的时间。

值得注意的是第2项,最近一次发现新路径的时间。如果由于目标二进制文件或者命令行参数出错,那么其执行路径应该是一直不变的,所以如果从fuzzing开始一直没有发现新的执行路径,那么就要考虑是否有二进制或者命令行参数错误的问题了。对于此状况,AFL也会智能地进行提醒odd check synax

我在测试dwebp库时遇到了这样的情况,这表明afl没有找到测试输入路径,解决方法是需要在输入fuzz指令时指定输入的测试路径,并加上-f参数指令强制覆盖结果内容。

2.overall results

这里包括运行的总周期数、总路径数、崩溃次数、超时次数。

其中,总周期数可以用来作为何时停止fuzzing的参考。随着不断地fuzzing,周期数会不断增大,其颜色也会由洋红色,逐步变为黄色、蓝色、绿色。一般来说,当其变为绿色时,代表可执行的内容已经很少了,继续fuzzing下去也不会有什么新的发现了。此时,我们便可以通过Ctrl-C,中止当前的fuzzing

3.stage progress

这里包括正在测试的fuzzing策略、进度、目标的执行总次数、目标的执行速度

执行速度可以直观地反映当前跑的快不快,如果速度过慢,比如低于500次每秒,那么测试时间就会变得非常漫长。如果发生了这种情况,那么我们需要进一步调整优化我们的fuzzing

在运行时发现,afl进行时会占用一个cpu内核,由于虚拟机在安装时设置使用两个内核,所以普通的afl最多只能同时进行两个afl测试

Read More
post @ 2023-12-23

实验环境:Ubuntu12.04,因为后面的版本补上了脏牛漏洞

原理:看教材8.5

MAP_PRIVATE写时拷贝

MADV_DONTNEED丢弃

对COW用write3个步骤中2~3存在竞态条件

创建两个进程:一个写write,一个放弃madvise。执行顺序:madvise要放在write的2~3之间。While1不停循环,两个进程不停竞争,用CTRL+C结束程序

目标:放弃私有拷贝,直接在映射文件上写入

Task1:改变一个只读的dummy文件

创建一个dummy文件

在root文件夹下,对其他用户只读,若尝试写入会发现权限不够

尝试将/zzz文件的aaaaa处修改为******,5个a要修改为6个*,那么写时会覆盖掉后面一个字符b

Task2:改变密码文件试图获取root权限

创建新用户charlie,设置密码为CHARLIE123

修改脏牛攻击代码,用只读打开密码文件,找到存放charlie用户的记录并找到第三个用户id处,试图将其修改为0000,因为root的id是0,从而获取root权限

切换到charlie用户后,可以看到获取到root权限

Read More
post @ 2023-12-23

下载labsetup.zip:SEED Project (seedsecuritylabs.org)

2.1

关闭地址空间随机化,简化实验难度

2.2

编译。Makefile中已经提供了编译命令。在编译过程中,将看到一条警告消息。 此警告是由gcc编译器针对格式字符串漏洞实施的对策生成的。

2.3

进入 Labsetup文件夹,使用docker-compose.yml文件设置实验室环境。容器fmt-server-1(server-10.9.0.5),fmt-server-2(server-10.9.0.6)创建完成。

Task 1

正常输入:

接收到6个字符并输出笑脸,服务器没有崩溃

恶意输入:

格式规定符是%s,printf()函数把获得到的值视为一个地址,并打印出该地址处的字符串。而栈帧保存的值并不都是合法的地址,它们可能是0(null指针)、指向受保护内存的地址或者没有映射物理地址的虚拟地址。当程序试图从一个非法地址获取数据时,该程序将崩溃。

输出结果显示:接收%s这3个字符,然鹅没有后续输出和笑脸,说明程序崩溃

2打印出服务器程序的内存

2A

修改build_string.py,编写一个生成字符串的python程序task2A.py。运行它,使字符串(0x20210806的字节形式和100个“%x|”)保存在badfile2A中。

(%x:printf将参数视为unsigned int类型(4字节),并用十六进制的格式打印出来。当printf()遇到%x时,它打印出va_list指针指向的数,并将va_list推进4个字节)。

向服务器server-10.9.0.5提供输入,输入为badfile2A的内容。得到服务器打印出的内容。

将服务器输出中20210806|及之前根据%x打印出的字符串复制到一个文件count.txt中。

通过grep相关命令得到|的个数为64。即可以得到需要64个 %x 格式说明符才能让服务器程序打印出输入的前四个字节0x20210806(63个%x将va_list指针移动至输入的起始地址)。

2B

根据服务器的输出The secret message’s address: 0x080b4008,得到秘密信息的地址为0x080b4008。修改build_string.py,编写一个生成字符串的python程序task2B.py。运行它,使字符串(由0x080b4008的字节形式,63个”%x”和”\nsecret message:%s”构成)保存在badfileB中。

向服务器server-10.9.0.5提供输入,输入为badfile2B的内容。得到服务器打印出的内容。故秘密信息(secret message)为A secret message

3修改服务器程序的内存

此任务的目标是修改服务器程序中定义的目标变量的值(我们将继续使用 10.9.0.5)。target 的原始值为 0x11223344。假设这个变量拥有一个重要的值,它会影响程序的控制流程。如果远程攻击者可以改变它的值,他们就可以改变这个程序的行为。我们有三个子任务。

3A改为不同的值

根据服务器的输出The target variable’s address: 0x080e5068,得到目标变量(target variable)的地址为0x080e5068。编写一个生成字符串的python程序task3A.py。运行它,使字符串(0x080e5068的字节形式、63个”.%x”、1个”%n”、结尾”\n”)保存在badfile3A中。

(当printf()遇到%n时,它会获取va_list指针指向的值,将视该值为一个内存地址,然后将数据(已打印出的字符的个数)写入该地址)。

向服务器server-10.9.0.5提供输入,输入为badfile3A的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0x00000010c。

为什么是0x10c呢?从输出看出,打印了66*4=264个字符(一行打印66个字符,共打印4行)再加上content开头4字节由小端法插入的number地址,所以264+4=268(0x10c)

至于为什么打印出来很多||...|连在一起,因为中间8个0省略打印

3B

0x5000=20480=4+62*(8+1)+19917+1,故编写一个生成字符串(0x080e5068的字节形式(长度为4个字符)、62个”%.8x|”、1个”%.19917x”、1个”\n”、1个”%n”)的python程序task3B.py。运行它,使该字符串保存在badfile3B中。

(精度修饰符形如”.number“,当应用于整型值时,它控制最少打印多少位字符)

向服务器server-10.9.0.5提供输入,输入为badfile3B的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0x00005000。

3C

因为0xAA=170<4*64,故选用%hn(视参数视为2字节字符型数),即每次只修改两个字节的值。根据小端法,目标变量(target variable)从最高两位字节的地址为0x080e506a、最低两位字节的地址为0x080e5068。

故字符串开头为数字0x080e506a+”@@@@”+数字0x080e5068。共12个字符。

(printf()通过格式化字符%x经过”@@@@”才能改变%n对应数据——已打印出的字符的个数,给下一个地址赋更大的值)

0xAABB=43707,43707=12+693*62+729

0xCCDD-0xAABB=8738。

故字符串由0x080e506a的字节形式、”@@@@”、0x080e5068的字节形式、62个”%.693x”、1个”%.729x”、1个”%hn”、1个”%.8738x”、1个”%hn”、结尾”\n”组成。

编写一个生成该字符串的python程序task3C.py。运行它,使该字符串保存在badfile3C中。

向服务器server-10.9.0.5提供输入,输入为badfile3C的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0xaabbccdd。

4将恶意代码注入服务器程序

目标是将将myprintf函数返回地址修改为shellcode的入口地址。

执行恶意代码:

修改exploit.py,编写task4.py,再次输入命令到服务器查看The input buffer’s address和Frame Pointer (inside myprintf),按照这两个值用于修改buf_addr和ebp_addr。

将shellcode放在输入的尾部。使用%.numberx移动va_list指针并修改已打印字符的值,.number为精度修饰符。然后选用%hn,单次修改2个字节,将myprint的返回地址(ebp+4)修改为shellcode的入口地址。补全后的python程序task4.py及相关注释如下:

执行程序task4.py,得到badfile。向服务器server-10.9.0.5提供输入,输入为badfile的内容。得到服务器打印出的内容。可以看到,服务器执行了shellcode中预置的命令ls -l。

获取反向shell

使用ifconfig -a 查看攻击者服务器的ip地址是10.9.0.1。

修改task4.py中的32位shellcode_32中的恶意代码,-i表示是一个交互式shell,重定向输入输出和标准错误到tcp连接的10.9.0.1的9090端口,使得在攻击端实现输入和看到输出:

打开攻击端口,使用nc -nv -l 9090对端口9090进行监听。

打开目标端口,运行exploit.py,得到badfile。向服务器server-10.9.0.5提供输入,输入为badfile的内容。

在攻击端可以得到服务器server-10.9.0.5的shell,获取root权限:

5攻击64位服务器程序

新目标是10.9.0.6,它运行64位版本的格式化程序。让我们首先向该服务器发送一条hello消息。我们将看到目标容器打印出以下消息。

修改exploit.py,编写task5.py,输入hello基本命令到服务器查看The input buffer’s address和Frame Pointer (inside myprintf),按照这两个值用于修改buf_addr和ebp_addr。

根据任务2.A的经验,可以得到输入的起始地址是printf()第34个参数的位置。

修改task4.py中的64位shellcode_64中的恶意代码,-i表示是一个交互式shell,重定向输入输出和标准错误到tcp连接的10.9.0.1的9090端口,使得在攻击端实现输入和看到输出:

64 位地址带来的挑战:

对于每个地址(8 个字节),最高的两个字节始终为00,对应Ascii码\0。在攻击中,我们需要将地址放在格式字符串中。 对于 32 位程序,我们可以将地址放在任何地方,因为地址内没有00。 对于64 位程序,我们不能再这样做了。 如果将地址放在格式字符串的中间,当 printf() 解析格式字符串时,它会在遇到\0时停止解析。 基本上,格式字符串中第一个零(\0)之后的任何内容都不会被视为格式字符串的一部分。\0引起的问题与缓冲区溢出攻击不同,在缓冲区溢出攻击中,如果使用 strpcy() ,\0将终止内存复制。

一个有用的技巧:自由移动参数指针

​在格式字符串中,我们可以使用%x将参数指针 va_list 移动到下一个可选参数。 我们也可以直接将指针移动到第k个可选参数。这是使用格式字符串的参数字段(以 k$ 的形式)完成的。 以下代码示例使用%3$.20x打印出第 3 个可选参数(数字 3)的值,然后使用%6$n将值写入第 6 个可选参数(变量 var,它的值将变为 20)。 最后,使用 %2$.10x,它将指针移回第二个可选参数(数字 2),并将其打印出来。 可以看到,使用这个方法,我们可以自由地来回移动指针。 此技术对于简化此任务中格式字符串的构造非常有用。

对策:

将shellcode放在输入的尾部。目标是将将myprintf函数返回地址修改为shellcode的入口地址。这里使用%hn一次修改两字节的值,其中返回地址最高两字节的值0x0000无需改变。

因为在64位中shellcode的入口地址是包含0x00的。将地址转换为字节时,00对应的Ascii码为\0。当 printf()解析格式化字符串时,在遇到零(\0)后会停止解析,之后的任何内容都不会被视为格式化字符串的一部分。故myprintf函数返回地址的所在地址的高四字节和低四字节应该放在格式化字符串的后面。

故先用%.numberx修改已打印字符数,然后使用%k$n将指针移动到printf()的第k个参数,这个参数应为要修改的值的地址。

格式化字符串的构成为:

”%.number1x”、“%k1$hn”、“%.number2x”、 “%k2$hn”、“%.number3x”、“%k3$hn”。

分别是所需要的数的大小,和所指向的参数的序号。分别计算。

关于k1、k2、k3的取值:输入最多为1500个字节,故k1、k2、k3各自最多占3个字节。

number1、number2、number3的取值不会超过FFFFFFFF,即4294967295,各自最多占11个字节。

故整个格式化字符串最多为63个字节,占不到8个参数的长度。

对应地将myprint函数的返回地址的0-8位、8-16位、16-24位所在地址,按照数值shellcode的入口地址的0-8位、8-16位、16-24位数值从小到大的顺序放在第42、43和44个参数的位置(34+8=42,34是需要多少个%x才能到修改地址处,8是格式化字符串占的),相对与输入起始地址的字节大小分别为64、72、80。

故k1=64,k2=72,k3=80。

类似于32位,我的exploit.py实现了自动化计算,每次使用只需修改两个服务器输出的地址The input buffer’s address和Frame Pointer (inside myprintf)即可。exploit.py代码如下:

打开攻击端口,使用nc -nv -l 9090对端口9090进行监听。

再打开目标端口,运行task5.c,得到badfile。向服务器server-10.9.0.6提供输入,输入为badfile的内容。

在攻击端口可以得到服务器server-10.9.0.6的shell,获取root权限:

6解决问题

gcc 编译器生成的警告信息,意思是:字符串格式并不是一个常量,而且没有格式化字符串的参数。

修复漏洞

将format.c中printf(msg)更改为printf(“%s”,msg),并重新编译,发现没有警告。

重新建立并开启docker,对32位服务器的target值进行攻击。发现target的值并没有改变,故攻击失败。

Read More
post @ 2023-12-23

下载labsetup.zip:SEED Project (seedsecuritylabs.org)

2.1

关闭地址空间随机化,简化实验难度

2.2

编译。Makefile中已经提供了编译命令。在编译过程中,将看到一条警告消息。 此警告是由gcc编译器针对格式字符串漏洞实施的对策生成的。

2.3

进入 Labsetup文件夹,使用docker-compose.yml文件设置实验室环境。容器fmt-server-1(server-10.9.0.5),fmt-server-2(server-10.9.0.6)创建完成。

Task 1

正常输入:

接收到6个字符并输出笑脸,服务器没有崩溃

恶意输入:

格式规定符是%s,printf()函数把获得到的值视为一个地址,并打印出该地址处的字符串。而栈帧保存的值并不都是合法的地址,它们可能是0(null指针)、指向受保护内存的地址或者没有映射物理地址的虚拟地址。当程序试图从一个非法地址获取数据时,该程序将崩溃。

输出结果显示:接收%s这3个字符,然鹅没有后续输出和笑脸,说明程序崩溃

2打印出服务器程序的内存

2A

修改build_string.py,编写一个生成字符串的python程序task2A.py。运行它,使字符串(0x20210806的字节形式和100个“%x|”)保存在badfile2A中。

(%x:printf将参数视为unsigned int类型(4字节),并用十六进制的格式打印出来。当printf()遇到%x时,它打印出va_list指针指向的数,并将va_list推进4个字节)。

向服务器server-10.9.0.5提供输入,输入为badfile2A的内容。得到服务器打印出的内容。

将服务器输出中20210806|及之前根据%x打印出的字符串复制到一个文件count.txt中。

通过grep相关命令得到|的个数为64。即可以得到需要64个 %x 格式说明符才能让服务器程序打印出输入的前四个字节0x20210806(63个%x将va_list指针移动至输入的起始地址)。

2B

根据服务器的输出The secret message’s address: 0x080b4008,得到秘密信息的地址为0x080b4008。修改build_string.py,编写一个生成字符串的python程序task2B.py。运行它,使字符串(由0x080b4008的字节形式,63个”%x”和”\nsecret message:%s”构成)保存在badfileB中。

向服务器server-10.9.0.5提供输入,输入为badfile2B的内容。得到服务器打印出的内容。故秘密信息(secret message)为A secret message

3修改服务器程序的内存

此任务的目标是修改服务器程序中定义的目标变量的值(我们将继续使用 10.9.0.5)。target 的原始值为 0x11223344。假设这个变量拥有一个重要的值,它会影响程序的控制流程。如果远程攻击者可以改变它的值,他们就可以改变这个程序的行为。我们有三个子任务。

3A改为不同的值

根据服务器的输出The target variable’s address: 0x080e5068,得到目标变量(target variable)的地址为0x080e5068。编写一个生成字符串的python程序task3A.py。运行它,使字符串(0x080e5068的字节形式、63个”.%x”、1个”%n”、结尾”\n”)保存在badfile3A中。

(当printf()遇到%n时,它会获取va_list指针指向的值,将视该值为一个内存地址,然后将数据(已打印出的字符的个数)写入该地址)。

向服务器server-10.9.0.5提供输入,输入为badfile3A的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0x00000010c。

为什么是0x10c呢?从输出看出,打印了66*4=264个字符(一行打印66个字符,共打印4行)再加上content开头4字节由小端法插入的number地址,所以264+4=268(0x10c)

至于为什么打印出来很多||...|连在一起,因为中间8个0省略打印

3B

0x5000=20480=4+62*(8+1)+19917+1,故编写一个生成字符串(0x080e5068的字节形式(长度为4个字符)、62个”%.8x|”、1个”%.19917x”、1个”\n”、1个”%n”)的python程序task3B.py。运行它,使该字符串保存在badfile3B中。

(精度修饰符形如”.number“,当应用于整型值时,它控制最少打印多少位字符)

向服务器server-10.9.0.5提供输入,输入为badfile3B的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0x00005000。

3C

因为0xAA=170<4*64,故选用%hn(视参数视为2字节字符型数),即每次只修改两个字节的值。根据小端法,目标变量(target variable)从最高两位字节的地址为0x080e506a、最低两位字节的地址为0x080e5068。

故字符串开头为数字0x080e506a+”@@@@”+数字0x080e5068。共12个字符。

(printf()通过格式化字符%x经过”@@@@”才能改变%n对应数据——已打印出的字符的个数,给下一个地址赋更大的值)

0xAABB=43707,43707=12+693*62+729

0xCCDD-0xAABB=8738。

故字符串由0x080e506a的字节形式、”@@@@”、0x080e5068的字节形式、62个”%.693x”、1个”%.729x”、1个”%hn”、1个”%.8738x”、1个”%hn”、结尾”\n”组成。

编写一个生成该字符串的python程序task3C.py。运行它,使该字符串保存在badfile3C中。

向服务器server-10.9.0.5提供输入,输入为badfile3C的内容。得到服务器打印出的内容。得到目标变量(target variable)的值变为0xaabbccdd。

4将恶意代码注入服务器程序

目标是将将myprintf函数返回地址修改为shellcode的入口地址。

执行恶意代码:

修改exploit.py,编写task4.py,再次输入命令到服务器查看The input buffer’s address和Frame Pointer (inside myprintf),按照这两个值用于修改buf_addr和ebp_addr。

将shellcode放在输入的尾部。使用%.numberx移动va_list指针并修改已打印字符的值,.number为精度修饰符。然后选用%hn,单次修改2个字节,将myprint的返回地址(ebp+4)修改为shellcode的入口地址。补全后的python程序task4.py及相关注释如下:

执行程序task4.py,得到badfile。向服务器server-10.9.0.5提供输入,输入为badfile的内容。得到服务器打印出的内容。可以看到,服务器执行了shellcode中预置的命令ls -l。

获取反向shell

使用ifconfig -a 查看攻击者服务器的ip地址是10.9.0.1。

修改task4.py中的32位shellcode_32中的恶意代码,-i表示是一个交互式shell,重定向输入输出和标准错误到tcp连接的10.9.0.1的9090端口,使得在攻击端实现输入和看到输出:

打开攻击端口,使用nc -nv -l 9090对端口9090进行监听。

打开目标端口,运行exploit.py,得到badfile。向服务器server-10.9.0.5提供输入,输入为badfile的内容。

在攻击端可以得到服务器server-10.9.0.5的shell,获取root权限:

5攻击64位服务器程序

新目标是10.9.0.6,它运行64位版本的格式化程序。让我们首先向该服务器发送一条hello消息。我们将看到目标容器打印出以下消息。

修改exploit.py,编写task5.py,输入hello基本命令到服务器查看The input buffer’s address和Frame Pointer (inside myprintf),按照这两个值用于修改buf_addr和ebp_addr。

根据任务2.A的经验,可以得到输入的起始地址是printf()第34个参数的位置。

修改task4.py中的64位shellcode_64中的恶意代码,-i表示是一个交互式shell,重定向输入输出和标准错误到tcp连接的10.9.0.1的9090端口,使得在攻击端实现输入和看到输出:

64 位地址带来的挑战:

对于每个地址(8 个字节),最高的两个字节始终为00,对应Ascii码\0。在攻击中,我们需要将地址放在格式字符串中。 对于 32 位程序,我们可以将地址放在任何地方,因为地址内没有00。 对于64 位程序,我们不能再这样做了。 如果将地址放在格式字符串的中间,当 printf() 解析格式字符串时,它会在遇到\0时停止解析。 基本上,格式字符串中第一个零(\0)之后的任何内容都不会被视为格式字符串的一部分。\0引起的问题与缓冲区溢出攻击不同,在缓冲区溢出攻击中,如果使用 strpcy() ,\0将终止内存复制。

一个有用的技巧:自由移动参数指针

​在格式字符串中,我们可以使用%x将参数指针 va_list 移动到下一个可选参数。 我们也可以直接将指针移动到第k个可选参数。这是使用格式字符串的参数字段(以 k$ 的形式)完成的。 以下代码示例使用%3$.20x打印出第 3 个可选参数(数字 3)的值,然后使用%6$n将值写入第 6 个可选参数(变量 var,它的值将变为 20)。 最后,使用 %2$.10x,它将指针移回第二个可选参数(数字 2),并将其打印出来。 可以看到,使用这个方法,我们可以自由地来回移动指针。 此技术对于简化此任务中格式字符串的构造非常有用。

对策:

将shellcode放在输入的尾部。目标是将将myprintf函数返回地址修改为shellcode的入口地址。这里使用%hn一次修改两字节的值,其中返回地址最高两字节的值0x0000无需改变。

因为在64位中shellcode的入口地址是包含0x00的。将地址转换为字节时,00对应的Ascii码为\0。当 printf()解析格式化字符串时,在遇到零(\0)后会停止解析,之后的任何内容都不会被视为格式化字符串的一部分。故myprintf函数返回地址的所在地址的高四字节和低四字节应该放在格式化字符串的后面。

故先用%.numberx修改已打印字符数,然后使用%k$n将指针移动到printf()的第k个参数,这个参数应为要修改的值的地址。

格式化字符串的构成为:

”%.number1x”、“%k1$hn”、“%.number2x”、 “%k2$hn”、“%.number3x”、“%k3$hn”。

分别是所需要的数的大小,和所指向的参数的序号。分别计算。

关于k1、k2、k3的取值:输入最多为1500个字节,故k1、k2、k3各自最多占3个字节。

number1、number2、number3的取值不会超过FFFFFFFF,即4294967295,各自最多占11个字节。

故整个格式化字符串最多为63个字节,占不到8个参数的长度。

对应地将myprint函数的返回地址的0-8位、8-16位、16-24位所在地址,按照数值shellcode的入口地址的0-8位、8-16位、16-24位数值从小到大的顺序放在第42、43和44个参数的位置(34+8=42,34是需要多少个%x才能到修改地址处,8是格式化字符串占的),相对与输入起始地址的字节大小分别为64、72、80。

故k1=64,k2=72,k3=80。

类似于32位,我的exploit.py实现了自动化计算,每次使用只需修改两个服务器输出的地址The input buffer’s address和Frame Pointer (inside myprintf)即可。exploit.py代码如下:

打开攻击端口,使用nc -nv -l 9090对端口9090进行监听。

再打开目标端口,运行task5.c,得到badfile。向服务器server-10.9.0.6提供输入,输入为badfile的内容。

在攻击端口可以得到服务器server-10.9.0.6的shell,获取root权限:

6解决问题

gcc 编译器生成的警告信息,意思是:字符串格式并不是一个常量,而且没有格式化字符串的参数。

修复漏洞

将format.c中printf(msg)更改为printf(“%s”,msg),并重新编译,发现没有警告。

重新建立并开启docker,对32位服务器的target值进行攻击。发现target的值并没有改变,故攻击失败。

Read More
post @ 2023-12-23

软件安全测试第三次实验

姓名:secret! 班级:secret! 学号:secret!

1 实验概况

实验教材:Buffer_Overflow_Setuid.pdf (seedsecuritylabs.org)

实验环境:Ubuntu 20.04

2 Setup

关闭地址空间随机化(ASLR)这个针对缓冲区溢出攻击的防御措施。ASLR对程序内存中的一些关键数据区域进行随机化,包括栈的位置、堆和库的位置等,目的是让攻击者难以猜测到所注入的恶意代码在内存中的具体位置。

为了使实验成功,需要使用一个没有实现保护机制的shell,只要把/bin/sh指向这个叫做zsh的shell程序

3 Task1:熟悉shellcode

3.1 shellcode.c

通过execve()系统调用执行一个shell程序

不能编译c代码以及生成的二进制文件用于shellcode攻击,原因如下:

加载器问题:缓冲区溢出攻击中恶意代码不由操作系统加载,而是直接通过内存复制载入的,因此初始化步骤缺失导致无法运行这个shell程序

代码中的0:字符串复制例如strcpy()遇到\0停止,发现这个c代码编译为二进制代码后至少会出现三处0:

字符串“/bin/sh”末尾

程序有两个NULL

name[0]中的0是否转化为二进制中的0取决于编译环境

由于上述问题,不能使用由C语言程序生成的二进制代码作为恶意代码,需要直接用汇编语言来写,shellcode最核心的部分是使用execve()系统调用来执行“/bin/sh”,这个系统调用需要设置以下4个寄存器:

eax寄存器:保存11,这是execve()的系统调用号

ebx寄存器:保存命令字符串的地址(如“/bin/sh”)

ecx寄存器:保存参数数组的地址

edx寄存器:保存新程序的环境变量的地址,可设为0表示不传递任何环境变量

解读shellcode代码步骤(以32位为例):

第一步:找到“/bin/sh”字符串在内存中的地址并设置ebx。为了找到“/bin/sh”的地址,需要把这个字符串压入栈中。由于一次只能压入4字节,故把字符串分成3份一次压入。最后movl %esp %ebx将字符串地址保存到ebx寄存器中。后续实验中调试可以通过打印ebx的值获知shell关键恶意代码的位置

第二步:找到name数组的地址并设置ecx。Name数组由2个元素:第一个存放”/bin/sh”,第二个放空指针。已知ebx保存了字符串“b/bin/sh”的地址,也就是name数组首地址。最后指令movl %esp %ecx表示ecx寄存器保存着name数组的首地址

第三步:将edx设为0。用异或方法清空edx寄存器

第四步:调用execve()系统调用。需要两条指令:将系统调用到11保存到eax;中断将系统切换到内核

实验中先设置好环境,然后尝试编译运行观察可见顺利调用到/bin/sh

4 Task2:理解stack.c

4.1解释Stack.c

上述程序存在缓冲区溢出漏洞。它首先从一个名为badfile的文件中读取一个输入,然后将这个输入传递到函数bof()中的另一个缓冲区。原始输入的最大长度可以为517字节,但bof()中的缓冲区只有BUF SIZE字节长,小于517字节。strcpy()不检查边界,就会发生缓冲区溢出。由于这个程序是一个root拥有的Set-UID程序,如果一个普通用户可以利用这个缓冲区溢出漏洞,用户可能能够获得一个root shell。需要注意的是,该程序从一个名为badfile的文件中获取其输入。这个文件在用户的控制之下。现在,我们的目标是为badfile创建内容,这样当易受攻击的程序将内容复制到它的缓冲区时,就可以生成一个root shell。

4.2编译

编译。要编译上述易受攻击的程序,不要忘记使用-fno-stack-protector和“-z执行堆栈”选项关闭StackGuard和不可执行的堆栈保护。编译后,我们需要使程序成为root拥有的Set-UID程序。我们可以通过首先将程序的所有权更改为root,然后将权限更改为4755以启用Set-UID位来实现这一点。需要注意的是,更改所有权必须在打开Set-UID位之前完成,因为所有权更改会导致Set-UID位被关闭。

编译和设置命令已经包含在Makefile中,所以我们只需要键入make来执行这些命令。变量L1、…、L4在Makefile中设置;它们将在编译期间使用。

5 Task3:攻击32位程序(Level1)

5.1 gdb调试

要利用目标程序中的缓冲区溢出漏洞,最需要知道的是缓冲区的起始位置和存储返回地址的地方之间的距离。我们将使用一种调试方法来找出它。由于我们有目标程序的源代码,我们可以在调试标志打开的情况下编译它。那将使调试更加方便。我们将在gcc命令中添加-g标志,因此调试信息被添加到二进制文件中。如果运行make,则调试版本已经创建。我们将使用gdb来调试stack-L1-dbg。我们需要在运行程序之前创建一个名为badfile的文件

注1、当gdb在bof()函数内部停止时,它在ebp寄存器设置为指向当前堆栈帧之前就停止了,所以如果我们在这里打印出ebp的值,我们就会得到调用者的ebp值。我们需要使用next来执行几条指令,并在ebp寄存器修改为指向bof()函数的堆栈帧后停止。

注2、需要注意的是,从gdb获得的帧指针值与实际执行时(不使用gdb)不同。这是因为gdb在运行调试程序之前,已经将一些环境数据推送到堆栈中。当程序不使用gdb直接运行时,堆栈没有这些数据,所以实际的帧指针值会更大。

gdb调试代码解释:

b bof : 在bof()函数设置断点

run: 在gdb模式下run

next : 执行下一句(进行strcpy(buffer,str))

p $ebp: 获取调用函数栈帧基地址

p &buffer: 获取buffer地址

5.2 发起攻击 exploit.py

实验要求:要利用目标程序中的缓冲区溢出漏洞,我们需要准备一个有效负载,并将其保存在badfile中。我们将使用Python程序来做到这一点。我们提供了一个名为exploit.py的骨架程序,该程序包含在实验室设置文件中。代码不完整,学生需要替换代码中的一些基本值。

代码解释:

栈溢出漏洞导致数据溢出到相邻的内存区域。buffer是一个数组,用于存储恶意文件的内容。通过在payload中溢出buffer的边界,我们可以覆盖控制流中的返回地址。

shellcode存放恶意代码,32位的汇编版本

L20创建一个长度为517个字节的content数组,并用0x90(NOP)填充整个数组,

L25表示把恶意代码放在该数组的尾部

Ret=ebp+100返回地址

Offset=ebp+4-buffer偏移量

L等于4是32位程序

Ret:Ebp+4是返回地址地址;ebp+8是第一条NOP指令地址,NOP指令什么也不做,只告诉CPU往前走,因此只要猜中任意一个NOP指令地址就可以一直往前走,最终走到恶意代码的真正注入点(猜测注入代码的准确入口地址ret)又因为调试比运行栈帧更深,用ebp+100,写入返回地址字段中

Offset:返回地址字段在输入数据中处于那个位置?输入将被复制到buffer,为了让输入中的返回地址字段准确覆盖栈中的返回地址字段区域,需要知道栈中buffer和返回地址区域之间的距离,这个距离就是返回地址字段在输入数据中的位置,offset=返回地址-buffer=(ebp+4)-buffer=112

完成上述程序后,运行它。这将生成badfile的内容。然后运行易受攻击的程序堆栈。编译运行成功获得root

6 Task4:攻击时不知道缓冲区大小(Level2)

在这个任务下,我们不知道 buffer 的大小,只知道是 100 到 200。但是,我们还是可以使用 gdb 得到 &buffer 的地址,只是不能得到 $ebp 的地址罢了。我们先把 ret 的值修改为 &buffer,然后把函数每个可能的 ret 位置都修改为我们的 ret,并且将 start 设为517 - len(shellcode)。

用返回地址进行范围覆盖,注意ret要在覆盖范围之外,确保跳转到设置的返回地址。

Offset从112开始因为由上一题的offset计算出

完整代码解释:

这段代码是一个简单的漏洞利用脚本,用于生成一个恶意文件。

首先,脚本导入了sys模块,这是Python的标准库,用于与Python解释器进行交互。

接下来,定义了一个变量shellcode,它包含了一个恶意代码的字节表示。这段代码是一个shellcode,用于执行特定的操作,例如获取系统权限或执行恶意操作。在这个例子中,shellcode是一个简单的Linux/x86的反弹shell。

然后,定义了一个名为content的字节数组,用于存储恶意文件的内容。这个数组的长度是517字节,填充了NOP指令(0x90)。

在注释下方的代码块中,将shellcode插入到payload中的某个位置。首先,计算了shellcode应该插入的起始位置start,然后将shellcode的字节复制到content数组中的相应位置。

接下来,定义了一个返回地址ret,它是一个指向payload中某个位置的地址。这个地址将决定程序执行完shellcode后的下一条指令的位置。在这个例子中,返回地址是一个固定的值。

然后,使用一个循环将返回地址的字节复制到payload中的多个位置。这些位置是通过offset变量计算得出的,它的范围是从112到212,每次增加4个字节。这些位置将被用作程序执行完shellcode后的返回地址。

最后,将content数组的内容写入一个名为badfile的二进制文件中。

总结来说,这段代码的目的是生成一个恶意文件,其中包含了一个shellcode和一些返回地址。当这个文件被执行时,shellcode将被执行,并且程序将跳转到返回地址指定的位置。

7 Task5:攻击64位程序(Level3)

由于64位下地址最高位2个字节总是0,就导致strcpy的时候遇到0停止,复制到栈中的时候内容不全。

具体而言,一个64 位地址0x00007FFFFFFFFFFF 也就是地址中有 0,在执行 strcpy(buffer, str); 的时候遇到 0 就停止了,0后面的内容不会被拷贝。地址还是小端序,ret 的低位非 0 会先拷贝,高位 0 不拷贝。这也就意味着 shellcode 不能放在返回地址的后面,只能放在返回地址的前面,所以调整start和ret

offset同样方法计算=返回地址-buffer=(rbp+8)-buffer

start的值被设置为96,是为了将shellcode放置在payload的适当位置。在这个例子中,start的值是根据实际情况进行调整的,以确保shellcode被正确放置在payload中,并且不会覆盖其他重要的数据。

Ret:由于shellcode 不能放在返回地址的后面,只能放在返回地址的前面,所以是返回地址-64是减号

8 攻击64位程序(Level4)

不知道缓冲区大小,实际缓冲区大小很小,所以不能使用task5的方法,否则把返回地址也一并覆盖了。

可以修改返回地址,跳转到str上的shellcode而不是使用buffer中的。

在gdb调试中查看rbp、buffer和str的地址

Start的值使得shellcode放在后面容错率高一些,ret适当偏移量,offset是str到返回区域的距离

经过实验可见成功拿到root权限

9 Task7:击败dash的对策

在 ubuntu 中, dash shell 检测到有效的 UID 和真实 UID 不相等就会放弃特权。前面我们将 sh 链接到 zsh 来解决的这个问题,现在我们来尝试新的对策。首先链接回原来的

在调用 execve() 之前将真实 ID 修改为 0 即可,也就是调用 setuid(0)。我们把 call_shellcode.c 中注释掉的二进制代码加入到 shellcode 的开头就行了。

可见添加setuid(0)前uid是用户,但是添加后重新编译运行能够拿到root权限

重复level1,可见在sh链接到 dash 的情况下我们拿到了 root 权限:

10 Task8:击败地址随机化

输入命令使得栈堆地址随机化,攻击者无法猜测固定地址注入点,实验重新运行level1可见不成功

突破32位计算机的栈随机化,通过暴力的方式不断重复发起缓冲区溢出攻击,希望碰巧猜对内存地址

输入命令sh ./addr_random.sh,经实验用了57秒猜对地址,成功拿到root权限

11 Task9:尝试其他对策

Stackgurad:在返回地址和缓冲区之间设置一个哨兵,用这个哨兵来检测返回地址是否被修改。当缓冲区溢出攻击修改返回地址时,所有处于缓冲区和返回地址之间的内存值也会被修改。

重新编译去除-fno-stack-protector,取消关闭stackguard选项,将badfile作为恶意输入,gcc检查到缓冲区溢出输出stack smashing

去除 -z execstack(程序的栈可执行的),编译 call_shellcode.c 并运行,可以发现栈不再执行。

不可执行堆栈只是使得无法在堆栈上运行shellcode,但并不能防止缓冲区溢出攻击,因为在利用缓冲区溢出漏洞后,还有其他方法可以运行恶意代码

Read More
post @ 2023-12-23
  1. 实验bash功能

Ubuntu16.04中/bin文件夹下有bash和有漏洞的bash_shellshock程序,利用教材代码进行实验,测试有漏洞的bash_shellshock和无漏洞的bash,结果如下:

foo是一个变量,以分号隔开两行指令,前面是函数定义,后面是一行命令

echo命令打印变量,declare命令打印函数

export后作为环境变量传递给子进程,而子进程是bash程序,则会将环境变量转换成一个shell变量,但当发现()开始则会转换成shell函数,所以declare能打印函数而不能打印变量,shellshock漏洞会将{}后面的指令执行出来

具体转化过程:(export之后)

Bash检查环境变量是否(){开头,若是则转化为函数定义

调用parse_and_execute()函数解析函数定义,这个函数功能很强大,不仅能解析函数定义,还能解析和运行shell 指令,所以;隔开的多个shell命令也会被解析执行

Shellshock漏洞条件:目标进程是必运行bash+攻击者通过函数有函数定义的环境变量传给目标进程

在无漏洞的bash中子进程不会将原来的字符串变量转换成函数而是依旧是一个变量,所以declare打印为空

  1. 建立CGI进程

CGI程序是使用Shell脚本编写的。因此,在执行CGI程序之前,将首先调用shell程序,并且这种调用是由用户从远程计算机触发的。

在我们的设置中,我们从同一台计算机上运行Web服务器和攻击,这就是为什么使用localhost的原因。在实际攻击中,服务器在远程计算机上运行,我们使用服务器的主机名或IP地址来代替localhost。

需要将CGI程序放在目标服务器的/usr/lib/cgi-bin目录下,并将权限设置为可执行的775,root权限完成,curl发送HTTP请求,可见执行命令echo打印出Hello World

只写一个CGI脚本,未涉及攻击

  1. 通过环境变量给bash传输数据

CGI请求则服务器会fork新建子进程,然后execve执行

Shellshock攻击成功的两个条件:

触发bash:

CGI程序以#!/bin/bash_shellshock开头所以是一个shell脚本,execve执行的是/bin/bash_shellshock,调用bash执行shell脚本

通过环境变量为bash提供输入:

最后一行指令strings /proc/$$/environ能打印一个进程的所有环境变量,bash将¥$$替换成当前进程ID

通过curl访问CGI程序

可见成功输出环境变量

  1. 发起shellshock攻击

HTTP请求头中的User-Agent字段客户端是curl,因为通过curl客户端访问,赋值给环境变量HTTP_USER_AGENT

Curl -A选项可以设置User-Agent字段

攻击者为User-Agent构造字符串用来触发bash错误解析,例如简单的指令/bin/ls -l /etc/shadow,观察是否会打印这个root的文件信息

但无法查看/etc/shadow的内容,因为需要特权所以无法查看

ls -l打印数据库/var/www/CSRF/Elgg/elgg-config/settings.php的文件权限信息

构造/bin/cat /var/.../settings.php 攻击的原理是 -A 后面的字符串会作为 http头中 的 agent 发送给服务器,apache agent 会构造相应的环境变量传给 shell,有漏洞的shell 看到环境变量以 A=() { 会识别成shell函数,将=替换成空格,也就是A () {,而后调用强大的函数解析函数体{echo hello;};在}后的分号后面的指令执行,从而被攻击。

|grep pass查看数据库密码

  1. 通过shellshock创建反向shell

反向Shell是在计算机上启动的Shell进程,其输入和输出由远程计算机的某人控制。基本上,shell在受害者的计算机上运行,但是它从攻击者的计算机上获取输入,并在攻击者的计算机上打印其输出。

原理同样是:curl -A 设置HTTP头USER-AGETN,赋值给环境变量HTTP-USER-AGENT,shell转化环境变量时发现(){开头转化为函数,这样的话USER_AGENT被转化为一个shell函数和三个shell命令并执行。

在同一台虚拟机上打开两个终端,分别作为攻击者和目标者,用ipconfig查看ip为127.0.0.1

首先在攻击者终端搭建服务器,监听8000端口,输出listening...

然后在目标者终端输入命令,-i表示shell交互式,输出重定向至TCP连接的127.0.0.1的8000端口,0<&1从网络里读取输入,2>&1错误输出也重定向到网络连接。回车后可在攻击者终端看到响应

用户id为www-data

  1. 用无漏洞bash重做3和5

重做3:

修改cgi程序,改为/bin/bash修补版本

复制到/usr/lib/cgi-bin目录下并改为root可执行,成功打印出环境变量,结果与bash_shellshock相同,因为curl没有涉及攻击(比如-A修改HTTP头等),功能时两个shell正常提供的功能

重做5:

在攻击端监听后,到目标端运行恶意指令

可见攻击端没有响应,且输出结果在目标端而不在攻击端,说明反向shell创建失败,只是执行了cgi程序中的打印环境变量,因为bash打补丁后不会再错误的将环境变量解析为函数并分号后面的指令

Read More

软件安全测试第一次实验

__姓名:secret! 班级:secret! 学号:secret!

一、实验题目

Environment_Variable_and_SetUID.pdf (seedsecuritylabs.org)

二、实验环境

Dell 14pro 灵越5410

VMware17

Ubuntu 22.04

Linux 5.19.0 x86_64

三、实验内容

1. 设置环境变量

Task 1.1

打印环境变量(截图中的输出未展示全部)

显示当前路径

Task 1.2

导入/撤销环境变量

  1. 子进程如何从父进程获取环境变量

myprinten.c

结论:结合教材所学知识,fork()创建子进程会继承父进程的环境变量,execve()调用会丢失环境变量,需要作为参数显式地传递。

  1. 环境变量受execve的影响

execve.c

(截图中的输出未展示全部)

原因分析:首先传入NULL环境变量为空,然后修改第三个参数environ显式传入成功打印环境变量

结论:execve若在一个进程中使用,环境变量会丢失,必须要作为参数显式地传递

原有进程的内存被完全替换成新程序,execve不会创建新的进程,PID没有变化,exec只是用磁盘上的新程序替换了当前进程的正文,数据,堆,栈段。所以环境变量也被替换了。

  1. 环境变量受system()影响

system.c

(截图中的输出未展示全部)

结论:可见实验中打印出所有环境变量,说明system函数会将环境变量传递给新程序

system()调用实际上是调用shell执行命令,system()的实现是用execl()系统调用来运行shell然后execve()传递环境变量数组,所以用system系统调用,当前进程的环境变量会传递给新程序。

  1. 环境变量和setuid程序

编译运行后设置为setuid程序,导入3个环境变量,再分别运行打印

结论:

所有的在父进程shell设置的环境变量都传递到了setUid程序子进程。shell中执行程序,当输入程序名称时,shell会生成一个子进程并在子进程中执行该程序.这个过程通常为先使用fork函数创建一个子进程,再使用execve()函数。

设置LD_LIBRARY_PATH环境变量(动态库的查找路径)不会出现在子进程的环境变量中,而其他两个均可以被包含。这是由于setuid程序的防御机制,当真实用户和有效用户不一致会自动忽略这个环境变量,而自定义的usrname没有危害仍可以打印

  1. PATH环境变量和setuid程序

编译运行后设置为setuid程序,将/bin/sh复制到另一个路径下并添加到环境变量,查看环境变量检查添加成功:tmp目录最先被搜索,然后运行程序,得到root权限

原因分析:虽然程序中命令为ls,但实际上运行了自己设定的程序/bin/sh,同时由于程序为Set-UID,有效用户为root,运行时以root权限运行,得到root权限的shell.

  1. LD_PRELOAD环境变量和setuid程序

Step1

进行动态链接和环境变量设置,编译源代码

Step2

情形一

常规程序,普通用户运行,执行自定义函数

情形二

设置setuid再运行,正常sleep1s后退出

情形三

在setuid基础上加入环境变量后再运行,正常sleep1s后退出

情形四:

将拥有者设置为第三方用户,成功运行重载sleep函数

设置为setuid程序再运行,不会运行重载函数:

结果分析:

动态链接器对Setuid程序有防御机制,当拥有者存在不一致时会忽略设置的环境变量;设置了Set-UID的程序,运行时不会出现函数重载

  1. 使用system()与excve()调用外部程序

Step1

编译并setuid系列操作,查看权限:

在当前目录下创建不为空文件test8。将所有者改为root并用root身份设置只读权限,(这样操作使得普通用户无法操作test8)。运行程序,查看test8内容并执行输入行指令rm test8,成功删除test8文件

Step2

注释system系统调用代码,改用execve

不会得到root权限

结果分析:

System系统调用函数实际执行三个操作:首先fork创建一个子进程;然后子进程中调用exec函数去执行command;在父进程中调用wait等待子进程结束。

其中在第二步引入了外部程序shell,(希望这个setuid执行shell:shell先被执行,command作为输入然后解析执行,是通过调用/bin/bash -c command;shell程序允许一行输入两个命令,用分号隔开),而在shell中,是可以执行任何指令的,所以实验中可以执行多条指令,在设置setuid程序后,可以获取root权限。system()函数违背了最小权限原则,调用了shell。

execve函数为执行一个系统调用函数,包含3个函数参数,其中第2个参数若包含额外指令也会被视作一个参数。

  1. 权限泄露

命令行解释:

首先以普通用户运行发现无法打开因为没有/etc/zzz的root权限

然后ls -l查看可执行文件的权限

以root身份向/etc/zzz写入,然后退出登录降低身份为普通用户

以普通用户身份写入/etc/zzz仍然没有权限

但是运行setuid程序之后,输出文件描述符并能够成功写入

结果分析:

Cap_leak.c中有打开文件open代码但是没有对应关闭文件close代码,并且setuid()撤销权限时泄露把握文件描述符的能力,也就是取消权限前并没有关闭文件,导致普通用户通过打印文件描述符fd拥有对文件操作的特权

避免方法:

法一:退出root之前,取消可执行文件cap_leak的set位权限即setuid,将/etc/zzz的权限位除拥有者root外其他用户其他组都没有任何读写执行的权限;

法二:在cap_leak.c中注释撤销权限代码setuid(),以防普通用户从中获得特殊权限。

修改后运行结果如下:

成功阻止普通用户写入

Read More
post @ 2023-12-23

信息安全数学基础

课程实验报告

实验报告(一)

系 别: 网络空间安全系

班 级: secret!

姓 名: secret!

学 号: secret!

实验时间: 2023年10月17日

指导老师: secret!

湖南大学信息科学与工程学院

  1. 实验内容

  1. 实验目标

理解求逆元和欧拉函数原理以及证明,并利用c++代码实现,并在基础上优化

  1. 实验原理

3.1 求逆元

根据教材内容证明过程可知,求解逆元有两种方法:

方法一:定义a*a’%m=1

方法二:广义欧几里得除法s*a+t*m=1,a’=s

3.2 求欧拉函数

结合欧拉函数定义和求解公式可知,求解过程如下:

遍历小于n的素数,减去能整除这些素数的个数,遍历完毕如果还是素数则进一步减去自身的倍数,剩下的个数是欧拉函数

  1. 实验设计

4.1求逆元

4.1.1方法一:定义

4.1.1.1 代码思路

(1)首先,从标准输入中读取两个整数a和m。

(2)定义一个变量inv_a,并初始化为-1。这个变量用来存储逆元。

(3)使用一个for循环,从1到m-1遍历所有可能的逆元。

(4)在循环中,通过判断(a * i) % m == 1来找到逆元。如果找到了逆元,将其赋值给inv_a,并使用break语句跳出循环。

(5)最后,将逆元inv_a输出到标准输出。

这段代码的时间复杂度为O(m),因为需要遍历所有可能的逆元,适合m较小时遍历求解。

4.1.2 方法二:

4.1.2.1 代码思路

使用了扩展欧几里得算法来计算两个整数的逆元:

(1)定义了一个函数exgcd,用于计算a和b的最大公约数,并且通过引用参数x和y返回扩展欧几里得算法的结果。

在exgcd函数中,首先判断b是否为0,如果是,则将x设置为1,y设置为0,并返回a作为最大公约数。a*x+b*y=a

如果b不为0,则递归调用exgcd函数,将b和a%b作为参数,并将y和x的值交换,以便在递归返回时正确计算x和y的值。

在递归返回后,通过以下公式计算x和y的值:y -= a/b * x;

最后,exgcd函数返回最大公约数d。

(2)在main函数中,首先从输入中读取两个整数a和m。

然后定义了两个变量x和y,用于存储逆元的计算结果。

调用exgcd函数,将a和m作为参数,并将计算结果存储在x和y中。

接下来,通过判断最大公约数d是否等于1来确定是否存在逆元。如果d不等于1,则输出”没有逆元”。

如果d等于1,则计算逆元的值,并使用取模运算确保结果在范围(0, m)内,然后输出结果(x%m+m)%x,因为x就是逆元,但是这样处理保证数值为正

4.1.2.2 x和y是什么

在这段代码中,x和y是exgcd函数的参数,它们是用来存储扩展欧几里得算法计算过程中的中间结果。

具体来说,x和y分别表示方程ax + by = d中的未知数。在扩展欧几里得算法中,我们通过递归的方式计算最大公约数d,并且在计算过程中更新x和y的值。

在函数的开始,我们将x和y的初始值设为1和0,这是因为当b等于0时,方程变为ax + 0y = a,所以x的系数为1,y的系数为0。

然后,在递归调用中,我们将y赋值给x,将a % b赋值给y,并且通过递归计算最大公约数d。最后,我们通过y -= a / b * x来更新y的值,以便在递归返回时得到正确的结果。

因此,x和y在这段代码中起到了存储中间结果的作用,用于计算最大公约数和更新系数的值。

举一个例子:

当我们使用扩展欧几里得算法来计算两个数的最大公约数时,我们需要通过递归调用来更新变量 x 和 y 的值。交换 x 和 y 的位置是为了确保在递归返回时得到正确的结果。

假设我们要计算 a = 30 和 b = 18 的最大公约数,并找到满足方程 ax + by = d 的整数解。

首先,我们进行第一次递归调用 exgcd(b, a % b, y, x),其中 b = 18,a % b = 12。我们将 y 和 x 的引用传递给递归调用。

在递归调用中,我们再次进行递归调用 exgcd(b, a % b, y, x),其中 b = 12,a % b = 6。这一次,我们将 y 和 x 的引用传递给递归调用。

在递归调用中,我们再次进行递归调用 exgcd(b, a % b, y, x),其中 b = 6,a % b = 0。这一次,我们将 y 和 x 的引用传递给递归调用。

由于 a % b = 0,递归调用结束,我们开始递归返回。在递归返回的过程中,我们更新 x 和 y 的值。

在第一次递归返回时,我们有 x = 1,y = 0。这是因为在 b = 6 和 a % b = 0 的情况下,方程 ax + by = d 变为 6x + 0y = 6,所以 x 的系数为 1,y 的系数为 0。

在第二次递归返回时,我们有 x = 0,y = 1。这是因为在 b = 12 和 a % b = 6 的情况下,方程 ax + by = d 变为 12x + 6y = 6,所以 x 的系数为 0,y 的系数为 1。

在第三次递归返回时,我们有 x = 1,y = -2。这是因为在 b = 18 和 a % b = 12 的情况下,方程 ax + by = d 变为 18x + 12y = 6,所以 x 的系数为 1,y 的系数为 -2。

最终,我们得到的最大公约数为 d = 6,并且满足方程 ax + by = d 的整数解为 x = 1,y = -2。

通过交换 x 和 y 的位置,我们可以确保在递归返回时,x 对应的是方程 ax + by = d 中 x 的系数,y 对应的是方程中 y 的系数。这样,我们可以得到正确的结果。

4.1.2 测试优化

但是在测试时发现,当n输入为很大超出INT_MAX时,根据范围对数据类型进行修改,改为long long类型:

4.2 欧拉函数

4.2.1代码思路

(1)初始化一个变量ans为n,用于保存最终的欧拉函数值。

(2)使用一个循环从2开始遍历到n的平方根。在循环中,判断n是否能被当前的循环变量i整除,如果可以整除,则说明i是n的一个素因子。

(3)如果i是n的素因子,将ans减去ans/i,这是因为ans/i表示小于等于n且与n互质的数中,能被i整除的数的个数。

(4)在一个内部的while循环中,将n不断除以i,直到n不能再被i整除为止。这是为了将n中的所有i素因子全部约掉。

(5)如果循环结束后,n仍然大于1,则说明n本身是一个素数,将ans减去ans/n。

(6)最后,返回ans作为欧拉函数的值。

总结起来,这段代码通过遍历n的素因子,将与n互质的数的个数逐步减去,最终得到欧拉函数的值。

4.2.2 测试优化

但是在测试时发现,当n输入为10^10时结果超出INT_MAX,所以根据范围对数据类型进行修改,改为long long类型:

  1. 实验结果

5.1求逆元

5.1.1编译

方法一编译:

方法二编译:

5.1.2 运行

测试书中样例:

输入a=2,m=7,输出逆元4;

输入a=635,m=737,输出逆元513

输入很大的数:

虽然结果是没有逆元是正确的,但是其实是巧合,因为INT_MAX=2147483646也跟2不互质没有逆元,输入a=1时同理,因为1的逆元是1本身

当将m换成与2互质且大于INT_MAX的数理应有逆元,但是输出“没有逆元”错误

但是即使修改成long long类型还是运行3个小时还无法算出结果,但是在方法二中可以输出结果

方法二long long优化代码输出结果:

经定义a*a’%m=1验证2*11666666666667=23333333333334-23333333333333=1

5.2欧拉函数

5.2.1编译:

5.2.2运行测试:

输入10

输入100:

输入10^10:

超出INT_MAX范围,输出错误

优化后:(将int改为long long)

以上结果验证均正确

六、总结

本次实验用c++编程实现了求解逆元和欧拉函数。

其中根据逆元的两种定义对应了两种求解思路,分别是尝试取余为1以及广义欧几里得算法。根据两种思路可以确定对应的求解适应背景,尝试法适用于较小数,而广义欧几里得算法适用于较大整数。在计算时产生中间结果较大不易用int类型保存,可优化为long long保存变量值。

求解欧拉函数,根据其基本定义是不大于整数n的素因子个数。那么遍历计数找其素因子,并将与n互质的数的个数逐步减去,最终得到欧拉函数的值。同理,在计算时产生中间结果较大不易用int类型保存,可优化为long long保存变量值,输入相同较大数进行测试看出优化后可以成功输出结果。

Read More
⬆︎TOP